package org.fujene.memoryblock;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.TreeMap;

import org.fujene.diskblock.repli.ReplicateContentDiskBlock;
import org.fujene.memcon.ReplicateIndexMemory;
import org.fujene.structure.Constants;
import org.fujene.structure.LookupPointer;
import org.fujene.toolbox.ByteConverter;

/**
 * ファイルに保存したメモリブロックを最適化し、いくつかのブロックをまとめて1つにします。
 * 使用時はインデックス・サーチともに不可能な状態で動作することを前提としています。
 * 
 * @author Fujisaka Yusuke
 */
public class Optimizer{
	private static final int PTR = Constants.BYTE_GLOBALPTR;
	private static final int STRLEN = Constants.BYTE_STRLEN;

	private ReplicateIndexMemory memoryInfo;
	private int ratio;

	public Optimizer(ReplicateIndexMemory memoryInfo, int ratio){
		this.memoryInfo = memoryInfo;
		this.ratio = ratio;
	}

	public void optimize(){
		int blockSize = memoryInfo.getBlockSize() * ratio;
		ByteArrayOutputStream stream = new ByteArrayOutputStream();
		TreeMap<LookupPointer, LookupPointer> changedPtrList = new TreeMap<LookupPointer, LookupPointer>();
		FileOutputStream writeFile = null;

		// Contents最適化
		int nextBlockID = memoryInfo.isNextFile(-1, Constants.MEMORY_TYPE_CONTENTS);
		int writeBlockID = -1;

		// Contentsの整理
		while(nextBlockID != Integer.MIN_VALUE){
			if(nextBlockID < 0){
				nextBlockID *= -1;
				if(writeBlockID == -1) writeBlockID = nextBlockID;
				// ディスク
				ReplicateContentDiskBlock diskBlock = (ReplicateContentDiskBlock)memoryInfo
						.getBlockFromDisk(nextBlockID);
				byte[] blockb = diskBlock.getData();
				// この段階で名前変更
				String deleteFile = memoryInfo.changeFileName(nextBlockID);

				int skipLength = 8 + PTR + (memoryInfo.contentInfo.getAppendixNum() * PTR);
				int strlen;
				for(int i = 0; i < blockb.length;){
					strlen = ByteConverter.byteToInt(blockb, i + skipLength, STRLEN);
					if(strlen > 0){
						// 削除IDかな？
						if(!memoryInfo.disk.deletedContentsInDisk.contains(new LookupPointer(nextBlockID, i))){
							changedPtrList.put(new LookupPointer(nextBlockID, i), new LookupPointer(
									writeBlockID, stream.size()));
							try{
								stream.write(Arrays.copyOfRange(blockb, i, i + skipLength + STRLEN + strlen));
							}catch(IOException e){}
							if(stream.size() > blockSize){
								try{
									writeFile = new FileOutputStream(new File(memoryInfo.dir + "/"
											+ writeBlockID + ".blk"));
									writeFile.write(stream.toByteArray());
									stream.flush();
									writeBlockID = nextBlockID;
								}catch(FileNotFoundException e){}catch(IOException e){}finally{
									try{
										writeFile.close();
									}catch(IOException e){}
								}
							}
						}
					}else{
						strlen *= -1;
					}
					i += skipLength + STRLEN + strlen;
				}
				
				new File(deleteFile).delete();
			}

			nextBlockID = memoryInfo.isNextFile(-1, Constants.MEMORY_TYPE_CONTENTS);
		}

		try{
			writeFile = new FileOutputStream(new File(memoryInfo.dir + "/" + writeBlockID + ".blk"));
			writeFile.write(stream.toByteArray());
			stream.flush();
			writeBlockID = nextBlockID;
		}catch(FileNotFoundException e){}catch(IOException e){}finally{
			try{
				writeFile.close();
			}catch(IOException e){}
		}

		// スキップポインタの整理
		nextBlockID = memoryInfo.isNextFile(-1, Constants.MEMORY_TYPE_SKIPPTRCONT);
		try{
			while(nextBlockID != -1){
				SkipPointerBlock skipPtrBlock = (SkipPointerBlock)memoryInfo.getBlockFromMemory(nextBlockID);

				byte[] skipptrb = skipPtrBlock.getData();
				LookupPointer skip;
				for(int j = 12; j < skipptrb.length; j += PTR){
					skip = ByteConverter.getPtr(Arrays.copyOfRange(skipptrb, j, j + PTR));
					if(changedPtrList.containsKey(skip))
						stream.write(ByteConverter.setPtr(changedPtrList.get(skip)));
					else stream.write(Arrays.copyOfRange(skipptrb, j, j + PTR));
				}

				nextBlockID = skipPtrBlock.getNextblock();
			}
		}catch(IOException e){}
	}
}
